package com.sdy.auth.client.filter;

import com.sdy.auth.api.AuthApi;
import com.sdy.auth.api.model.UserInfo;
import com.sdy.auth.client.config.SsoConfig;
import com.sdy.common.model.Response;
import com.sdy.common.model.Token;
import com.sdy.common.utils.EncodeUtil;
import com.sdy.fs.api.constant.FsConstant;
import com.sdy.mvc.config.SdyMvcConfig;
import com.sdy.mvc.utils.CookieUtil;
import com.sdy.mvc.utils.HttpUtil;
import com.sdy.mvc.utils.JsonUtil;
import com.sdy.common.utils.StringUtil;
import com.sdy.redis.service.RedisService;
import com.sdy.auth.client.service.SsoService;
import com.sdy.auth.client.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.UUID;

/**
 * Created by zzq on 2019/1/10.
 */
@Slf4j
@Component
public class SsoAuthenticationFilter extends AbstractAuthFilter {
    @Value("${res.app.code}")
    private String appCode;
    @Value("${res.app.noAuthentiation:/static,/favicon.ico,/opt/file}")
    private String noAuthentiation;
    @Autowired
    private SsoConfig ssoConfig;
    @Autowired
    private RedisService redisService;
    @Autowired
    private TokenService tokenService;
    @Autowired
    private AuthApi authApi;
    @Autowired
    private SsoService ssoService;
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String currentUrl = request.getRequestURI();
        // 过滤静态资源
        if (super.ignoreResource(servletRequest, noAuthentiation)) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        // 优先取url中的token，其次取cookie中的token
        String token = request.getParameter("token");
        if (token == null) {
            token = CookieUtil.getCookie(request, "token");
        }
        if (token == null) {
            // 获取JWT中的token
            String tokenEnc = request.getHeader("token");
            Token tk = tokenService.verifyToken(tokenEnc);
            if (tk != null) {
                request.setAttribute("token", tk);
                Token newToken = tokenService.reGenToken(tk);
                if (newToken != null) {
                    response.setHeader("token", newToken.getToken());
                }
                Integer userId = Integer.parseInt(tk.getSub());
                token = UUID.randomUUID().toString();
                UserInfo userInfo = new UserInfo().setUserId(userId);
                redisService.set(token, userInfo, SsoConfig.SESSION_SECOND_OUT);
            }
        }
        // 单点登录返回，获取从sso返回的token，写入cookie
        String randomStr = request.getParameter("token_enc");
        if (StringUtil.isNotBlank(randomStr)) {
            try {
                String redisKey = "sdy-sso_privateKey_:" + randomStr;
                String privateKeySpec = redisService.get(redisKey, String.class);
                if (privateKeySpec != null) {
                    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeySpec));
                    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                    PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
                    redisService.del(redisKey);
                    String base64Token = redisService.get("sdy-sso_token_" + randomStr, String.class);
                    redisService.del("sdy-sso_token_" + randomStr);
                    byte[] decryptedBytes = EncodeUtil.rsaDecrypt(EncodeUtil.hexStringToByte(base64Token), privateKey);
                    token = new String(decryptedBytes);
                    CookieUtil.setCookie(response, "token", token, true);
                }
            } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
                log.error("Decode algorithm error!", e);
            } catch (Exception e) {
                log.error("Decode data error!", e);
            }
        }
        UserInfo userInfo = null;
        if (StringUtil.isNotBlank(token)) {
            request.setAttribute("token", token);
            userInfo = redisService.get(token, UserInfo.class);
        }
        // 登出
        if (StringUtils.equals(currentUrl, SsoConfig.LOGOUT_URL)) {
            ssoService.logout(request);
            // 获取重定向地址
            String clientDomain = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
            if (StringUtil.isNotBlank(ssoConfig.getClientDomain())) {
                clientDomain = ssoConfig.getClientDomain();
            }
            String redirectUrl = ssoConfig.getServerDomain() + SsoConfig.LOGIN_PAGE_URL + "?targetUrl=" + clientDomain + "/" + "&appcode=" + appCode;
            String targetAsyncUrl = HttpUtil.removeUrlParam(request.getHeader("referer"), "token_enc");
            if (StringUtil.isBlank(targetAsyncUrl)) {
                targetAsyncUrl = clientDomain;
            }
            String redirectUrlAsync = ssoConfig.getServerDomain() + SsoConfig.LOGIN_PAGE_URL + "?targetUrl=" + URLEncoder.encode(targetAsyncUrl, "UTF-8") + "&appcode=" + appCode;
            if (HttpUtil.checkhost(ssoConfig.getServerDomain(), request.getServerName() + ":" + request.getServerPort()) && SsoConfig.LOGIN_PAGE_URL.equals(currentUrl)) {
                // 防止无限重定向
                response.sendRedirect(ssoConfig.getClientDomain().concat("/noaccess"));
                return;
            }
            redirect(request, response, redirectUrl, redirectUrlAsync);
            return;
        }
        // 统一返回文件上传地址
        if (currentUrl.startsWith(FsConstant.FS_UPLOAD_REQUEST)) {
            String uploadDomain = "";
            String respJson = JsonUtil.toJson(Response.success(uploadDomain));
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(respJson);
            response.getWriter().flush();
            response.getWriter().close();
            return;
        }
        if (userInfo == null) {
            // 未登录
            Response resp = authApi.checkAuthorization(null, currentUrl, appCode);
            if (!resp.getSuccess()) {
                return;
            }
            if (!Boolean.TRUE.equals(resp.getData())) {
                // 获取重定向地址
                String clientDomain = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
                if (StringUtil.isNotBlank(ssoConfig.getClientDomain())) {
                    clientDomain = ssoConfig.getClientDomain();
                }
                String redirectUrl = ssoConfig.getServerDomain() + SsoConfig.LOGIN_PAGE_URL + "?targetUrl=" + URLEncoder.encode(clientDomain, "UTF-8") + "/" + "&appcode=" + appCode;
                String targetAsyncUrl = HttpUtil.removeUrlParam(request.getHeader("referer"), "token_enc");
                if (StringUtil.isBlank(targetAsyncUrl)) {
                    targetAsyncUrl = clientDomain;
                }
                String redirectUrlAsync = ssoConfig.getServerDomain() + SsoConfig.LOGIN_PAGE_URL + "?targetUrl=" + URLEncoder.encode(targetAsyncUrl, "UTF-8") + "&appcode=" + appCode;
                if (HttpUtil.checkhost(ssoConfig.getServerDomain(), request.getServerName() + ":" + request.getServerPort()) && SsoConfig.LOGIN_PAGE_URL.equals(currentUrl)) {
                    // 防止无限重定向
                    response.sendRedirect(ssoConfig.getClientDomain().concat("/noaccess"));
                    return;
                }
                redirect(request, response, redirectUrl, redirectUrlAsync);
                return;
            }
        } else {
            // 已登录
            refreshToken(token, userInfo);
            // 在filter之间传递用户信息
            request.setAttribute("userInfo", userInfo);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    /**
     * 重定向
     */
    private void redirect(HttpServletRequest request, HttpServletResponse response, String redirectUrl, String redirectUrlAsync) throws IOException {
        String reqType = request.getHeader("req_type");
        // 重定向 ajax重定向 & 页面重定向
        if (StringUtil.equals(reqType, "async")) {
            response.setHeader("redirect", redirectUrlAsync);
            Response<String> respData = Response.success("Redirect to: ".concat(redirectUrlAsync));
            respData.setMessage(null);
            String respJson = JsonUtil.toJson(respData);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(respJson == null ? "" : respJson);
            response.getWriter().flush();
            response.getWriter().close(); //这里很重要，否则ajax的readystatus为0
        } else {
            response.sendRedirect(redirectUrl);
        }
    }

    /**
     * 登出
     */
    private void logout(String token) {
        if (StringUtils.isNotBlank(token)) {
            redisService.del(token);
        }
    }

    /**
     * 刷新token时间
     */
    private void refreshToken(String token, UserInfo userInfo) {
        boolean r = redisService.expire(token, SsoConfig.SESSION_SECOND_OUT);
        if (!r) {
            redisService.set(token, userInfo, SsoConfig.SESSION_SECOND_OUT);
        }
    }
}
